S01-18 JavaSE-多线程基础
[TOC]
线程相关概念
程序(program)
- 为完成特定任务,用某种语言编写的一组指令集合(即代码)
进程
- 运行中的程序(如 QQ、迅雷)
- 操作系统为进程分配独立内存空间
- 动态过程:有产生、存在、消亡的生命周期
线程
- 进程的实体,由进程创建
- 一个进程可包含多个线程(如 QQ 同时打开多个聊天窗口)
- 线程共享进程的内存空间
其他核心概念
| 概念 | 说明 |
|---|---|
| 单线程 | 同一时刻只允许执行一个线程 |
| 多线程 | 同一时刻可执行多个线程 |
| 并发 | 单核 CPU 中,多个任务交替执行(貌似同时) |
| 并行 | 多核 CPU 中,多个任务同时执行 |
线程基本使用
创建线程的两种方式
- 继承
Thread类,重写run方法 - 实现
Runnable接口,重写run方法
案例1:继承 Thread 类
java
package com.hspedu.threaduse;
/**
* 演示继承 Thread 类创建线程
* @author 韩顺平
* @version 1.0
*/
public class Thread01 {
public static void main(String[] args) throws InterruptedException {
// 创建线程对象
Cat cat = new Cat();
cat.start(); // 启动线程(底层调用 start0() 方法)
// 主线程继续执行
System.out.println("主线程继续执行" + Thread.currentThread().getName());
for (int i = 0; i < 60; i++) {
System.out.println("主线程 i=" + i);
Thread.sleep(1000);
}
}
}
class Cat extends Thread {
int times = 0;
@Override
public void run() { // 线程执行的业务逻辑
while (true) {
System.out.println("喵喵, 我是小猫咪" + (++times) + " 线程名=" + Thread.currentThread().getName());
try {
Thread.sleep(1000); // 休眠 1 秒
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80) {
break; // 执行 80 次后退出
}
}
}
}案例2:实现 Runnable 接口
java
package com.hspedu.threaduse;
/**
* 演示实现 Runnable 接口创建线程
* @author 韩顺平
* @version 1.0
*/
public class Thread02 {
public static void main(String[] args) {
Dog dog = new Dog();
// 创建 Thread 对象,传入 Runnable 实现类
Thread thread = new Thread(dog);
thread.start();
// 模拟 Thread 底层代理模式
Tiger tiger = new Tiger();
ThreadProxy threadProxy = new ThreadProxy(tiger);
threadProxy.start();
}
}
/**
* 线程代理类(模拟 Thread 底层实现)
*/
class ThreadProxy implements Runnable {
private Runnable target = null;
public ThreadProxy(Runnable target) {
this.target = target;
}
@Override
public void run() {
if (target != null) {
target.run(); // 动态绑定
}
}
public void start() {
start0(); // 模拟 start0() 本地方法
}
private void start0() {
run();
}
}
class Animal {}
class Tiger extends Animal implements Runnable {
@Override
public void run() {
System.out.println("老虎嗷嗷叫....");
}
}
class Dog implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("小狗汪汪叫..hi" + (++count) + " 线程名=" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}案例3:多线程并发执行
java
package com.hspedu.threaduse;
/**
* 两个线程并发执行
* @author 韩顺平
* @version 1.0
*/
public class Thread03 {
public static void main(String[] args) {
// 线程1:输出 hello,world 10 次
Thread thread1 = new Thread(new T1());
// 线程2:输出 hi 5 次
Thread thread2 = new Thread(new T2());
thread1.start();
thread2.start();
}
}
class T1 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello,world " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}
class T2 implements Runnable {
int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hi " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 5) {
break;
}
}
}
}继承 Thread vs 实现 Runnable
| 对比项 | 继承 Thread | 实现 Runnable |
|---|---|---|
| 继承限制 | 单继承,灵活性低 | 无继承限制,更灵活 |
| 资源共享 | 需静态变量实现共享 | 天然支持多线程共享资源 |
| 推荐度 | 较低 | 较高(推荐使用) |
线程终止
核心思想:通过标记变量控制 run 方法退出
java
package com.hspedu.exit_;
/**
* 线程终止示例
* @author 韩顺平
* @version 1.0
*/
public class ThreadExit_ {
public static void main(String[] args) throws InterruptedException {
AThread aThread = new AThread();
new Thread(aThread).start();
// 主线程执行 3 秒后终止子线程
for (int i = 0; i < 30; i++) {
System.out.println("main 线程运行中 " + i);
Thread.sleep(100);
if (i == 29) {
aThread.setLoop(false); // 通知子线程退出
}
}
}
}
class AThread implements Runnable {
private boolean loop = true; // 标记变量
@Override
public void run() {
while (loop) {
try {
Thread.sleep(50); // 休眠 50 毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("AThread 运行中....");
}
}
// 提供 set 方法修改标记
public void setLoop(boolean loop) {
this.loop = loop;
}
}线程常用方法
常用方法第一组
| 方法 | 功能 |
|---|---|
setName(String name) | 设置线程名称 |
getName() | 获取线程名称 |
start() | 启动线程(底层调用 start0()) |
run() | 线程执行的业务逻辑 |
setPriority(int newPriority) | 设置线程优先级(1-10) |
getPriority() | 获取线程优先级 |
sleep(long millis) | 让当前线程休眠指定毫秒数 |
interrupt() | 中断线程(常用于唤醒休眠线程) |
常用方法第二组
| 方法 | 功能 |
|---|---|
yield() | 线程礼让(让出 CPU,不一定成功) |
join() | 线程插队(插队线程执行完毕后,原线程才继续) |
案例:join 方法使用
java
package com.hspedu.method;
/**
* 线程插队示例
* @author 韩顺平
* @version 1.0
*/
public class ThreadMethodExercise {
public static void main(String[] args) throws InterruptedException {
Thread t3 = new Thread(new T3()); // 子线程
for (int i = 1; i <= 10; i++) {
System.out.println("hi " + i);
if (i == 5) {
t3.start(); // 启动子线程
t3.join(); // 子线程插队,执行完毕后主线程继续
}
Thread.sleep(1000);
}
}
}
class T3 implements Runnable {
private int count = 0;
@Override
public void run() {
while (true) {
System.out.println("hello " + (++count));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count == 10) {
break;
}
}
}
}用户线程和守护线程
| 线程类型 | 说明 | 示例 |
|---|---|---|
| 用户线程(工作线程) | 任务执行完或通知后结束 | 业务线程 |
| 守护线程 | 为用户线程服务,所有用户线程结束后自动退出 | 垃圾回收机制 |
守护线程示例
java
package com.hspedu.method;
/**
* 守护线程示例
* @author 韩顺平
* @version 1.0
*/
public class ThreadMethod03 {
public static void main(String[] args) throws InterruptedException {
MyDaemonThread dt = new MyDaemonThread();
dt.setDaemon(true); // 设置为守护线程
dt.start();
// 主线程执行 10 次后退出
for (int i = 1; i <= 10; i++) {
System.out.println("宝强辛苦工作....." + i);
Thread.sleep(50);
}
}
}
class MyDaemonThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("马蓉和宋喆快乐聊天,哈哈哈~~~");
}
}
}线程的生命周期
线程状态(Thread.State 枚举)
| 状态 | 说明 |
|---|---|
NEW | 线程尚未启动 |
RUNNABLE | 线程正在 Java 虚拟机中执行 |
BLOCKED | 线程被阻塞等待监视器锁定 |
WAITING | 线程等待另一个线程执行特定动作 |
TIMED_WAITING | 线程等待指定时间后自动唤醒 |
TERMINATED | 线程已退出 |
查看线程状态示例
java
package com.hspedu.state_;
/**
* 查看线程状态
* @author 韩顺平
* @version 1.0
*/
public class ThreadState_ {
public static void main(String[] args) throws InterruptedException {
T t = new T();
System.out.println(t.getName() + " 状态: " + t.getState()); // NEW
t.start();
while (Thread.State.TERMINATED != t.getState()) {
System.out.println(t.getName() + " 状态: " + t.getState()); // RUNNABLE/TIMED_WAITING
Thread.sleep(500);
}
System.out.println(t.getName() + " 状态: " + t.getState()); // TERMINATED
}
}
class T extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("hi " + i);
try {
Thread.sleep(1000); // 进入 TIMED_WAITING 状态
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}线程的同步
问题背景:多线程共享资源冲突(如售票超卖)
Synchronized 同步机制
- 同步代码块:java
synchronized (对象) { // 需同步的代码(同一时刻只能有一个线程执行) } - 同步方法:java
public synchronized void method() { // 需同步的代码 }
互斥锁
- 每个对象对应一个互斥锁标记
synchronized修饰的代码块/方法需获取对象锁才能执行- 静态同步方法的锁为
当前类.class - 非静态同步方法的锁为
this
案例:同步解决售票问题
java
package com.hspedu.syn;
import java.util.Vector;
/**
* 同步机制解决售票超卖问题
* @author 韩顺平
* @version 1.0
*/
public class SellTicket {
public static void main(String[] args) {
SellTicket03 sellTicket03 = new SellTicket03();
// 三个窗口同时售票
new Thread(sellTicket03, "窗口1").start();
new Thread(sellTicket03, "窗口2").start();
new Thread(sellTicket03, "窗口3").start();
}
}
class SellTicket03 implements Runnable {
private int ticketNum = 100; // 共享票数
private boolean loop = true;
Object object = new Object(); // 锁对象
@Override
public void run() {
while (loop) {
sell(); // 调用同步方法
}
}
/**
* 同步方法(锁为 this)
*/
public synchronized void sell() {
if (ticketNum <= 0) {
System.out.println("售票结束...");
loop = false;
return;
}
// 模拟售票延迟
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口" + Thread.currentThread().getName() + " 售出一张票" +
" 剩余票数=" + (--ticketNum));
}
}线程的死锁
死锁条件:多个线程互相持有对方需要的锁,且不肯释放
死锁示例
java
package com.hspedu.syn;
/**
* 模拟线程死锁
* @author 韩顺平
* @version 1.0
*/
public class DeadLock_ {
public static void main(String[] args) {
DeadLockDemo A = new DeadLockDemo(true);
DeadLockDemo B = new DeadLockDemo(false);
A.setName("A 线程");
B.setName("B 线程");
A.start();
B.start();
}
}
class DeadLockDemo extends Thread {
static Object o1 = new Object(); // 共享锁对象1
static Object o2 = new Object(); // 共享锁对象2
boolean flag;
public DeadLockDemo(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
// 线程A:先获取 o1,再尝试获取 o2
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入1");
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入2");
}
}
} else {
// 线程B:先获取 o2,再尝试获取 o1
synchronized (o2) {
System.out.println(Thread.currentThread().getName() + " 进入3");
synchronized (o1) {
System.out.println(Thread.currentThread().getName() + " 进入4");
}
}
}
}
}释放锁的场景
会释放锁的操作
- 同步代码块/方法执行结束
- 同步代码块/方法中遇到
break、return - 同步代码块/方法中抛出未处理的
Error或Exception - 同步代码块/方法中调用
wait()方法
不会释放锁的操作
- 调用
Thread.sleep()或Thread.yield()方法 - 调用
suspend()方法挂起线程(已过时)
本章作业
- Homework01.java(5min)
- 启动两个线程:线程1随机打印100以内整数,线程2读取键盘输入"Q"命令终止线程1
- Homework02.java(5min)
- 模拟银行取款:多个线程同时取款,每次取1000,余额不足时无法取款,避免超取(线程同步)